<?php
/**
 * 
 * Copyright (c) 2010, SRIT Stefan Riedel <info@srit.biz>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice, 
 * this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the documentation 
 * and/or other materials provided with the distribution.
 * - Neither the name of the author nor the names of its 
 * contributors may be used to endorse or promote products derived from 
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @category  TK
 * @package	  AsteriskApi
 * @author    Stefan Riedel <sr@srit.biz>
 * @copyright 2010 SRIT Stefan Riedel
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
 */

require_once 'IAsteriskApi.php';
require_once 'CRMAsteriskApiException.php';

/**
 * Base Class for accessing the Asterisk Manager interface 
 * {@link http://www.voip-info.org/wiki/view/Asterisk+manager+API}
 * 
 * @category TK
 * @package  AsteriskApi
 * @author    Stefan Riedel <sr@srit.biz>
 * @copyright 2010 SRIT Stefan Riedel
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
 */
class CRMAsteriskApiBase implements IAsteriskApiBase {
	/**
	 * end of line e.g.: \r\n
	 * @var string
	 */
	const END_OF_LINE = "\r\n";
	
	/**
	 * double end of line to send the command
	 * @var string
	 */
	const DOUBLE_END_OF_LINE = "\r\n\r\n";
	
	/**
	 * server host
	 * @var host
	 */
	protected $_server;
	
	/**
	 * the socket port
	 * @var int
	 */
	protected $_port = 5038;
	
	/**
	 * the connected socket to AMI
	 * @var object
	 */
	protected $_socket;
	
	/**
	 * the login user
	 * @varstring
	 */
	protected $_username;
	
	/**
	 * the login password
	 * @var string
	 */
	protected $_password;
	
	/**
	 * if autoConnect is true the class connect to asterisk server if necessary
	 * 
	 * @var bool
	 */
	protected $_autoConnect = false;
	
	/**
	 * sets to true if a user is logged in
	 * 
	 * @var bool
	 */
	protected $_loggedIn = false;
	
	/**
	 * if true the constructor will automaticly login
	 * @var bool
	 */
	protected $_autoLogin = false;
	
	/**
	 * the time to close the socket automaticly
	 * @var int
	 */
	protected $_socketTimeout = 3;
	
	/**
	 * $_server setter
	 * @see $_server
	 * @param string $server
	 */
	public function setServer($server) {
		$this->_server = $server;
	}
	
	/**
	 * $_server getter
	 * @see $_server
	 * @return string
	 */
	public function getServer() {
		return $this->_server;
	}
	
	/**
	 * $_port setter
	 * @see $_port
	 * @param int $port
	 */
	public function setPort($port) {
		$this->_port = ( int ) $port;
	}
	
	/**
	 * $_port getter
	 * @see $_port
	 * @return int
	 */
	public function getPort() {
		return ( int ) $this->_port;
	}
	
	/**
	 * $_socket getter
	 * @see $_socket
	 * @return int
	 */
	public function getSocket() {
		return $this->_socket;
	}
	
	/**
	 * $_autoConnect setter
	 * @see $_autoConnect
	 * @param bool $autoConnect
	 */
	public function setAutoConnect($autoConnect) {
		$this->_autoConnect = $autoConnect;
	}
	
	public function getAutoConnect() {
		return $this->_autoConnect;
	}
	
	public function setUsername($username) {
		$this->_username = $username;
	}
	
	public function getUsername() {
		return $this->_username;
	}
	
	public function setPassword($password) {
		$this->_password = $password;
	}
	
	public function getPassword() {
		return $this->_password;
	}
	
	public function getLoggedIn() {
		return ( bool ) $this->_loggedIn;
	}
	
	/**
	 * @return the $_autoLogin
	 */
	public function getAutoLogin() {
		return ( bool ) $this->_autoLogin;
	}
	
	/**
	 * @param $_autoLogin the $_autoLogin to set
	 */
	public function setAutoLogin($_autoLogin) {
		$this->_autoLogin = ( bool ) $_autoLogin;
	}
	
	/**
	 * @return the $_socketTimeout
	 */
	public function getSocketTimeout() {
		return $this->_socketTimeout;
	}
	
	/**
	 * @param $_socketTimeout the $_socketTimeout to set
	 */
	public function setSocketTimeout($_socketTimeout) {
		$this->_socketTimeout = $_socketTimeout;
	}
	
	public function __construct(array $config = array()) {
		if ($config !== array ()) {
			$this->_setPropertys ( $config );
		}
		if ($this->_autoConnect) {
			$this->connect ();
			if ($this->getAutoLogin ()) {
				$this->logIn ();
			}
		}
	}
	
	public function __destruct() {
		if ($this->isLoggedIn ()) {
			$this->logOff ();
		}
		if ($this->socketOpen ()) {
			$this->close ();
		}
	}
	
	public function connect(array $config = array()) {
		if ($config !== array ()) {
			$this->_setPropertys ( $config );
		}
		if (empty ( $this->_server )) {
			throw new CRMAsteriskApiException ( '$_server muss vor dem Konnektieren gesetzt sein!' );
		}
		$this->_socket = fsockopen ( $this->_server, $this->_port, $errno, $errstr );
		if ($this->_socket) {
			stream_set_timeout ( $this->_socket, $this->_socketTimeout );
			return true;
		}
		throw new CRMAsteriskApiException ( 'Bei der Verbindung ist ein Fehler aufgetreten. Code: ' . $errno . ' Meldung:' . $errstr );
	}
	
	public function socketOpen() {
		return $this->_socket !== null;
	}
	
	public function ping() {
		$this->_checkSocket ();
		$response = $this->_sendCommandToSocket ( "Action: Ping" . self::DOUBLE_END_OF_LINE, false );
		if ($this->_checkResponse ( "Pong", $response )) {
			return true;
		}
		return false;
	}
	
	public function logIn($username = null, $password = null) {
		$username = (null !== $username) ? $username : $this->_username;
		$password = (null !== $password) ? $password : $this->_password;
		if (empty ( $username ) || empty ( $password )) {
			throw new CRMAsteriskApiException ( 'Der Nutzername und das Passwort müssen gesetzt werden!' );
		}
		$this->_checkSocket ();
		$response = $this->_sendCommandToSocket ( "Action: login" . self::END_OF_LINE . "Username: " . $username . self::END_OF_LINE . "Secret: " . $password . self::DOUBLE_END_OF_LINE );
		if ($this->_checkResponse ( "Authentication accepted", $response ['Message'] )) {
			$this->_loggedIn = true;
			return true;
		}
		throw new CRMAsteriskApiException ( 'Authorisierung fehlgeschlagen.' );
	}
	
	public function logOff() {
		$this->_checkSocket ();
		$this->_sendCommandToSocket ( "Action: Logoff" . self::DOUBLE_END_OF_LINE );
		$this->_loggedIn = false;
		return true;
	
	}
	
	public function isLoggedIn() {
		return $this->getLoggedIn ();
	}
	
	public function close() {
		$this->_checkSocket ();
		if($this->isLoggedIn()) {
			$this->logOff();
		}
		$close = fclose ( $this->_socket );
		$this->_socket = null;
		return $close;
	}
	
	public function prepareNumberToCall($number) {
		
	}
	
	public function arrayToSocketCommand(array $socketCommand) {
	
	}
	
	protected function _checkResponse($needle, $haystack) {
		return strpos ( $haystack, $needle ) !== false;
	}
	
	/**
	 * @param config
	 * @throws CRMAsteriskApiException
	 */
	protected function _setPropertys($config) {
		$setter = '';
		foreach ( $config as $property => $value ) {
			$setter = 'set' . ucfirst ( $property );
			if (! method_exists ( $this, $setter )) {
				throw new CRMAsteriskApiException ( $property . ' ist nur lesbar!' );
			}
			$this->{$setter} ( $value );
		}
	}
	
	protected function _checkSocket() {
		if (! $this->socketOpen ()) {
			throw new CRMAsteriskApiException ( 'Es ist kein Socket zum Asterisk Server geöffnet!' );
		}
	}
	
	protected function _sendCommandToSocket($command, $retArray = true) {
		if (! fputs ( $this->_socket, $command )) {
			throw new CRMAsteriskApiException ( 'Das Kommando konnte nicht ausgeführt werden!' );
		}
		$response = stream_get_contents ( $this->_socket );
		if (! $response) {
			throw new CRMAsteriskApiException ( 'Der Server hat eine fehlerhafte Antwort gesendet.' );
		}
		if ($retArray) {
			$response = $this->_responseToArray ( $response );
		}
		return $response;
	}
	
	protected function _responseToArray($response) {
		$responseArray = array ();
		$lines = explode ( self::END_OF_LINE, $response );
		if (! empty ( $lines )) {
			foreach ( $lines as $line ) {
				if (! empty ( $line ) && strpos ( $line, 'Asterisk Call Manager' ) === false) {
					list ( $key, $message ) = explode ( ': ', $line );
					$responseArray [$key] = $message;
				}
			}
		}
		return $responseArray;
	}
}